package kGeom;

//hello

public class kLine extends kElement {
	public kVec	pt;
	public kVec	vec;
	
	/*
	 * CONSTRUCTORS
	 * 
	 * note, there is no reference constructor for kline due to the conflict between line(start,end) & line(pt,vec)
	 * 
	 */
	
	public kLine() {
		pt = new kVec();
		vec = new kVec(10,10);
	}
	
	public kLine(kVec p1, kVec p2) {
		pt = p1;
		vec = new kVec(p1,p2);
	}

	public kLine(kVec startPoint, kVec direction, float length) {
		pt = startPoint;
		vec = kVec.resize(direction, length);
	}
	
	public static kLine lineByStartPointAndVector(kVec point, kVec vector) {
		return new kLine(point, vector, vector.length());
	}	
	
	public static kLine invalid() {
		kLine ret = new kLine();
		ret.isValid = false;
		return ret;
	}		
	
	public kLine(float x1, float y1, float z1, float x2, float y2, float z2) {
		pt = new kVec(x1,y1,z1);
		vec = new kVec(pt, new kVec(x2,y2,z2) );
	}
	
	public kLine(float x1, float y1, float x2, float y2) {
		this( x1,y1,0,x2,y2,0);
	}

	/*
	 * METHODS
	 * 
	 * 
	 * 
	 */

	public kVec StartPoint() {
		return kVec.clone(pt);
	}	
	
	public kVec EndPoint() {
		return kVec.translate(pt,vec);
	}

	public float length(){
		return vec.length();
	}
	
	public kVec midPoint(){
		return kVec.translate(pt,vec, vec.length()/2);
	}
	
	public kVec inverseVec() {
		return kVec.invert(vec);
	}	
	
	/**
	 * @param t
	 * @return
	 * 		returns a point at a given t-value along a line
	 * 		0 will return start point
	 * 		1 will return end point
	 */
	public kVec eval(float t) {
		return kVec.translate(pt, vec, vec.length()*t);
	}	
	

	/**
	 * reverses the direction of the line
	 */
	public void reverse() {
		pt = eval(1);
		vec.invert();
	}

	/**
	 * translates the line by a given vector
	 * 
	 * @param vec
	 */
	public void translate(kVec vec) {
		pt = kVec.translate(pt,vec);
	}

	/**
	 * scales the line by a given factor about the (0,0) point
	 * 
	 * @param s
	 */
	public void scale(float s) {
		pt.scale(s);
		vec.scale(s);
	}

	public kVec closestPoint(kVec p) { return closestPoint(p, false); }
	public kVec closestPoint(kVec p, boolean treatLinesAsInfinite)
	{
		kVec diff = kVec.clone(p); diff.minus(this.StartPoint());
		kVec dir = kVec.clone(this.EndPoint()); dir.minus(this.StartPoint());
	    float t = diff.dot(dir)/dir.dot(dir);
	    
	    if (((t >= 0.0f)&&(t <= 1.0f))||(treatLinesAsInfinite)){
	    	dir.scale(t);
	    	kVec ret = this.StartPoint();
	    	ret.plus(dir);
	        return ret;
	    } else {
	    	if (t <= 0.0f) {return StartPoint();}
	    	else {return EndPoint();}
	    }
	}
	
	public float distanceTo(kVec p){
		return closestPoint(p).distance(p);
	}
	
	public boolean containsPoint(kVec p){
		kVec diff = kVec.clone(p); diff.minus(this.StartPoint());
		kVec dir = kVec.clone(this.EndPoint()); dir.minus(this.StartPoint());
	    float t = diff.dot(dir)/dir.dot(dir);
	    if ((t <= 0.0f)||(t >= 1.0f)) {
	        return false;
	    } else {
	    	return true;
	    }
	}
	
	public boolean isParallel(kLine ln){
		return (vec.isCoincident(ln.vec) || vec.isCoincident(ln.inverseVec()));
	}
	public boolean isParallel(kVec t){
		// test for direction?
		return vec.isCoincident(t);
	}
	public boolean isCoincident(kLine ln){
		if (!isParallel(ln)){return false;}
		if (ln.closestPoint(StartPoint(), true).distance(StartPoint()) == 0.0f){ return true; }
		return true;
	}
	public boolean isOverlapped(kLine ln){
		if (!isParallel(ln)){return false;}
		if (!isCoincident(ln)){return false;}
		
		if (ln.closestPoint(StartPoint()).distance(StartPoint()) == 0.0f){ return true; }
		if (ln.closestPoint(EndPoint()).distance(EndPoint()) == 0.0f){ return true; }
		
		if (closestPoint(ln.StartPoint()).distance(ln.StartPoint()) == 0.0f){ return true; }
		if (closestPoint(ln.EndPoint()).distance(ln.EndPoint()) == 0.0f){ return true; }		
		return false;
	}
	
	/**
	 * returns the line segment describing either the overlapping or non-overlapping portions of this line
	 * and a given line. if there is no overlap or if these line segments
	 * overlap only at a point, an invalid line segment is returned. by default,
	 * the direction of this kLine is preferred
	 * 
	 * @param ln
	 * @param non
	 * @return
	 */
	public kLine overlappingSegment(kLine ln, boolean returnOverlappingSegment) {
		if (!isOverlapped(ln)) {
			return kLine.invalid();
		}
		if (this.isEmbedded(ln)) {
			if (returnOverlappingSegment) {
				return ln;
			} else {
				// in this case, we would have to return two lines
				return kLine.invalid();
			}
		}
		if (ln.isEmbedded(this)) {
			if (returnOverlappingSegment) {
				return this;
			} else {
				// in this case, we would have to return two lines
				return kLine.invalid();
			}
		}

		kVec v1;
		kVec v2;
		if (returnOverlappingSegment){
			if (distanceTo(ln.StartPoint()) == 0.0f){v1 = ln.StartPoint();}
			else {v1 = ln.EndPoint();}		
			if (ln.distanceTo(StartPoint()) == 0.0f){v2 = StartPoint();}
			else {v2 = EndPoint();}
		} else {
			if (distanceTo(ln.StartPoint()) == 0.0f){v1 = ln.StartPoint();}
			else {v1 = ln.EndPoint();}		
			if (ln.distanceTo(StartPoint()) == 0.0f){v2 = EndPoint();}
			else {v2 = StartPoint();}
		}
		
		kLine ret = new kLine(v1,v2);
		if (ret.length() == 0.0f){ret.isValid = false;}
		return ret;
	}
	
	
	/**
	 * determines if A GIVEN LINE IS EMBEDDED WITHIN THIS LINE (not the other way around)
	 * (line ln is embedded in this)
	 * this logic seems a bit backwards when read out
	 * @param ln
	 * @return
	 */
	public boolean isEmbedded(kLine ln){
		if (!isParallel(ln)){return false;}
		if (!isCoincident(ln)){return false;}
		
		//if ((ln.closestPoint(StartPoint()).distance(StartPoint()) == 0.0f)&&(ln.closestPoint(EndPoint()).distance(EndPoint()) == 0.0f)){ return true; }
		if ((closestPoint(ln.StartPoint()).distance(ln.StartPoint()) == 0.0f)&&(closestPoint(ln.EndPoint()).distance(ln.EndPoint()) == 0.0f)){ return true; }
		return false;
	}
	
	
	public String toString(){
		return "line"+this.StartPoint().toString()+"->"+this.EndPoint().toString();
	}
	
	
}
